/* DwrServlet.java
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

package dowry.dwr;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import uk.ltd.getahead.dwr.*;
import uk.ltd.getahead.dwr.impl.*;
import dowry.*;

/**
 * DwrServlet augments the default servlet for DWR to
 * expose various custom Dowry objects via the DWR container
 * as well as to provide provide some special hooks to
 * receive notification of DWR lifecycle events.
 *
 */
public class DwrServlet extends DWRServlet
{
    public static final String DEFAULT_DATATYPE_MANAGER =
        "dowry.impl.DefaultDatatypeManager";

    public static final String DEFAULT_DOWRY_SERVICE =
        "dowry.impl.DefaultDowryService";

    public static final String DEFAULT_ENTITY_MAPPER =
        "dowry.impl.DefaultEntityMapper";

    public static final String DEFAULT_INTERCEPTOR =
        "dowry.impl.DefaultInterceptor";

    public static final String DEFAULT_JAVASCRIPT =
        "dowry.impl.DefaultJavascript";

    public static final String DEFAULT_PROCESSOR =
        "dowry.impl.DefaultProcessor";

    public static final String DEFAULT_PUBLISH_CONTAINER =
        "dwrContainer";

    public static final String INIT_PUBLISH_CONTAINER =
        "publishContainerAs";

    private Interceptor interceptor;

    /**
     * Augments the <code>init()</code> by initializing our
     * Interceptor and calling its <code>onInit</code>
     * lifecycle hook.
     *
     * @param cfg                 the servlet config
     *
     * @throws ServletException   if the DWR servlet
     *                            initialization throws an
     *                            exception
     *
     */
    public void init(ServletConfig cfg) throws ServletException
    {
        try
        {
            DwrConfig cfgWrapper = new DwrConfig(cfg);
            setDefaultParams(cfgWrapper);

            super.init(cfgWrapper);

            interceptor = (Interceptor)
                container.getBean(Interceptor.class.getName());

            interceptor.onInit(cfgWrapper);

            publishContainer(container, cfgWrapper);
        }
        catch(RuntimeException rex)
        {
            throw new ServletException(rex);
        }
    }

    /**
     * Augments the processing of a request to the DWR
     * servlet by adding lifecycle hooks
     * (<code>onInvoke</code>, <code>onSuccess</code>, and
     * <code>onError</code>) for the Interceptor.
     *
     * @param req                the servlet request
     *
     * @param res                the servlet response
     *
     * @throws ServletException  if the DWR processor throws
     *                           a servlet exception during
     *                           its processing
     *
     * @throws IOException       if the DWR processor throws
     *                           an io exception during its
     *                           processing
     *
     * @throws RuntimeException  if the DWR processor throws
     *                           a runtime exception during
     *                           its processing
     *
     */
    protected void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException
    {
        boolean          err              = true;
        ServletException servletException = null;
        IOException      ioException      = null;
        RuntimeException runtimeException = null;

        DwrRequest  reqWrapper = new DwrRequest(req);
        DwrResponse resWrapper = new DwrResponse(res);

        if (isInvocation(reqWrapper, resWrapper))
        {
            interceptor.onInvoke(reqWrapper, resWrapper);

            try
            {
                super.doPost(reqWrapper, resWrapper);
                err = resWrapper.isError();
            }
            catch (ServletException sex)
            {
                servletException = sex;
                err = true;
            }
            catch (IOException ioex)
            {
                ioException = ioex;
                err = true;
            }
            catch (RuntimeException rex)
            {
                runtimeException = rex;
                err = true;
            }

            if (err)
            {
                interceptor.onError(reqWrapper, resWrapper);
            }
            else
            {
                interceptor.onSuccess(reqWrapper, resWrapper);
            }

            if (servletException != null)
            {
                throw servletException;
            }
            else if (ioException != null)
            {
                throw ioException;
            }
            else if (runtimeException != null)
            {
                throw runtimeException;
            }
        }
        else
        {
            super.doPost(reqWrapper, resWrapper);
        }

        // if there hasn't been an exception,
        // commit the response...
        resWrapper.commit();
    }

    /**
     * Override <code>destory()</code> to call the
     * <code>onDestory</code> lifecycle hook for the
     * Interceptor.
     *
     */
    public void destroy()
    {
        interceptor.onDestroy();
        super.destroy();
    }

    /**
     * Checks if the request represents the a remote
     * invocation for DWR.
     *
     * @param req  the servlet request
     *
     * @param res  the servlet response
     *
     * @return     true, if a remote invocation; false
     *             otherwise
     *
     */
    protected boolean isInvocation(DwrRequest req,
                                   DwrResponse res)
    {
        String pathInfo = req.getPathInfo();
        if (pathInfo == null)
        {
            pathInfo = req.getServletPath();
        }

        return (pathInfo != null &&
                pathInfo.startsWith(HtmlConstants.PATH_EXEC));
    }

    /**
     * Publishes the DWR container into the servlet context,
     * so it can be used by others outside of DWR
     * invocations.  This more or less just adds in
     * functionality that is provided "out-of-the-box" in
     * DWR 2.0.
     *
     * @param container  the DWR container
     *
     * @param cfg        the servlet config
     *
     */
    protected void publishContainer(Container container, ServletConfig cfg)
    {
        String publishName = cfg.getInitParameter(INIT_PUBLISH_CONTAINER);
        if (publishName != null)
        {
            cfg.getServletContext().setAttribute(publishName, container);
        }
    }

    /**
     * Sets up the default init parameters for this servlet.
     * These will be consumed by the DWR container to
     * initialize the Dowry functionality.
     *
     * @param cfg  the servlet config
     *
     */
    protected void setDefaultParams(DwrConfig cfg)
    {
        setDefaultParam(cfg,
                        DatatypeManager.class.getName(),
                        DEFAULT_DATATYPE_MANAGER);

        setDefaultParam(cfg,
                        DowryService.class.getName(),
                        DEFAULT_DOWRY_SERVICE);

        setDefaultParam(cfg,
                        EntityMapper.class.getName(),
                        DEFAULT_ENTITY_MAPPER);

        setDefaultParam(cfg,
                        Interceptor.class.getName(),
                        DEFAULT_INTERCEPTOR);

        setDefaultParam(cfg,
                        Javascript.class.getName(),
                        DEFAULT_JAVASCRIPT);

        setDefaultParam(cfg,
                        Processor.class.getName(),
                        DEFAULT_PROCESSOR);

        setDefaultParam(cfg,
                        INIT_PUBLISH_CONTAINER,
                        DEFAULT_PUBLISH_CONTAINER);
    }

    /**
     * Sets the specified "default" init parameter if no
     * init parameter matching the specified name currently
     * exists.
     *
     * @param cfg   the servlet config
     *
     * @param name  the parameter name
     *
     * @param value  the parameter value
     *
     */
    protected void setDefaultParam(DwrConfig cfg,
                                   String name,
                                   String value)
    {
        String origValue = cfg.getInitParameter(name);
        if (origValue == null)
        {
            cfg.setInitParameter(name, value);
        }
    }
}